﻿using DocumentFormat.OpenXml.Packaging;
using Microsoft.SharePoint;
using SP.Exporters.Extractors;
using System;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;

namespace SP.Exporters
{
    /// <summary>
    /// An implementation of the SPExporter abstract class that exports a SP ListItem to a MS Word Document
    /// </summary>
    public class ListItemWordExporter : SPExporter
    {
        #region Properties

        /// <summary>
        /// Gets the list item.
        /// </summary>
        /// <value>
        /// The list item.
        /// </value>
        /// <exception cref="ExporterException">ListItemWordExporter can only export list items</exception>
        public SPListItem ListItem
        {
            get
            {
                var item = SPEntity as SPListItem;
                if (item == null) throw new ExporterException("ListItemWordExporter can only export list items");
                return item;
            }
        }

        /// <summary>
        /// Gets or sets the extractor factory.
        /// </summary>
        /// <value>
        /// The extractor factory.
        /// </value>
        public IFieldValueExtractorFactory ExtractorFactory { get; set; }

        /// <summary>
        /// Gets or sets the standard token value extractor.
        /// </summary>
        /// <value>
        /// The standard token value extractor.
        /// </value>
        public IStandardTokenValueExtractor StandardTokenValueExtractor { get; set; }

        #endregion

        #region Constructor Area

        /// <summary>
        /// Creates a new List Item Word Exporter. For the replacer factory, the default factory is assigned.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="templateFile">The template file.</param>
        public ListItemWordExporter(SPListItem item, SPFile templateFile)
            : base(item, templateFile)
        {
            ExtractorFactory = new DefaultWordFieldValueExtractorFactory();
            StandardTokenValueExtractor = new StandardTokenValueExtractor();
        }

        /// <summary>
        /// Creates a new List Item Word Exporter.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="templateFile">The template file.</param>
        /// <param name="factory">The factory.</param>
        public ListItemWordExporter(SPListItem item, SPFile templateFile, IFieldValueExtractorFactory factory)
            : base(item, templateFile)
        {
            ExtractorFactory = factory;
            StandardTokenValueExtractor = new StandardTokenValueExtractor();
        }

        #endregion

        #region Public

        /// <summary>
        /// Exports the list item to a word document..
        /// </summary>
        /// <returns>A stream with the document</returns>
        public override Stream Export()
        {
            var byteArray = TemplateFile.OpenBinary();
            var stream = new MemoryStream();
            stream.Write(byteArray, 0, byteArray.Length);
            try
            {
                using (var wordDoc = WordprocessingDocument.Open(stream, true))
                {
                    //additional configuration for the Extractor Factory
                    ConfigExtractorFactory(wordDoc);

                    wordDoc.MainDocumentPart.Document.InnerXml = UpdateXml(wordDoc.MainDocumentPart.Document.InnerXml);
                    foreach (var headerPart in wordDoc.MainDocumentPart.HeaderParts)
                    {
                        headerPart.Header.InnerXml = UpdateXml(headerPart.Header.InnerXml);
                    }
                    foreach (var footerPart in wordDoc.MainDocumentPart.FooterParts)
                    {
                        footerPart.Footer.InnerXml = UpdateXml(footerPart.Footer.InnerXml);
                    }
                }
            }
            catch (FileFormatException)
            {
            }
            return stream;
        }

        #endregion

        #region Template Update Area

        /// <summary>
        /// Updates the XML by searching for the predefined tokens, and replacing their contents.
        /// </summary>
        /// <param name="documentXml">The document XML.</param>
        /// <returns></returns>
        protected virtual string UpdateXml(string documentXml)
        {
            
            var regex = new Regex("#=[^=^#^<^>]*=#");
            var updatedDocumentXml = regex.Replace(documentXml, FieldValueTokenMatch);
            regex = new Regex("@@[^@^<^>]*@@");
            updatedDocumentXml = regex.Replace(updatedDocumentXml, BuiltInTokenMatch);
            return updatedDocumentXml;
        }

        #region Token Matches Evaluators

        /// <summary>
        /// Replaces the token #=    =# with the value of the item from the selected field.
        /// </summary>
        /// <param name="match">The match.</param>
        /// <returns>Formatted item value. Empty string if any errors occured (e.g. field does not exist)</returns>
        protected virtual string FieldValueTokenMatch(Match match)
        {
            var token = match.ToString();
            //extract formatting
            var regex = new Regex("{([^}]+)}");
            var formatMatch = regex.Match(token);
            var format = formatMatch.Success
                             ? formatMatch.ToString().Substring(1, formatMatch.ToString().Length - 2)
                             : String.Empty;

            var fieldName = formatMatch.Success
                                ? token.Replace(formatMatch.ToString(), String.Empty)
                                       .Replace("#=", String.Empty)
                                       .Replace("=#", String.Empty)
                                : token.Replace("#=", String.Empty).Replace("=#", String.Empty);
            SPField field;
            try
            {
                field = ListItem.ParentList.Fields.GetField(fieldName);
            }
            catch (ArgumentException)
            {
                return String.Empty;
            }
            var fieldValueReplacer = ExtractorFactory.CreateReplacer(field, format);
            return fieldValueReplacer.GetFieldValue(ListItem);
        }

        /// <summary>
        /// Replaces the token @@   @@   with the value of a built in token.
        /// </summary>
        /// <param name="match">The match.</param>
        /// <returns>Value of the token. Empty string if token is not defined</returns>
        protected virtual string BuiltInTokenMatch(Match match)
        {
            var token = match.ToString();

            //extract formatting
            var regex = new Regex("{([^}]+)}");
            var formatMatch = regex.Match(token);
            var format = formatMatch.Success
                             ? formatMatch.ToString().Substring(1, formatMatch.ToString().Length - 2)
                             : String.Empty;

            //extract field name
            var fieldName = formatMatch.Success
                                ? token.Replace(formatMatch.ToString(), String.Empty).Replace("@@", String.Empty)
                                : token.Replace("@@", String.Empty);
            fieldName = fieldName.ToUpper();
            return StandardTokenValueExtractor.GetFieldValue(fieldName, format);
        }

        #endregion

        #endregion

        #region Utility

        /// <summary>
        /// Provides additional configuration for the extractor factory.
        /// </summary>
        /// <param name="configObject">The config object.</param>
        protected virtual void ConfigExtractorFactory(object configObject)
        {
            var wordDoc = configObject as WordprocessingDocument;

            // set the MainDocumentPart for the DefaultWordFieldValueExtractorFactory
            if (wordDoc != null && ExtractorFactory is DefaultWordFieldValueExtractorFactory)
            {
                var defaultFactory = ExtractorFactory as DefaultWordFieldValueExtractorFactory;
                defaultFactory.MainDocumentPart = wordDoc.MainDocumentPart;
            }
        }


        #endregion
    }
}
